// "Copyright [2021] <Copyright Zhancheng Liang, Shengkai Lin>"

#include "cluster/network_controller/path_manager/OpticalPathManager.h"
#include "cluster/roce/RoceMsg_m.h"
#include "optical_device.h"


namespace ai_sim {

Define_Module(OpticalDevice);

void OpticalDevice::initialize() {
    opticaldevice_type = par("opticaldevice_type");
    switch_delay = par("switch_delay");
    numOptical_port = par("numOptical_port");
    my_index = par("my_index");

    // Below is necessary to simulate the optical devices!!
    for (int i = 0; i < numOptical_port; i++) {
        gate("optical_port$i", i)->setDeliverOnReceptionStart(true);
    }
}

void OpticalDevice::handleMessage(omnetpp::cMessage *msg) {
    Enter_Method_Silent();
    int input_port = msg->getArrivalGateId();
    // If is a normal data msg
    auto* ra_msg = dynamic_cast<RouteVecMsg*>(msg);
    auto* oxc_msg = dynamic_cast<OXCConfMsg*>(msg);
    EV << "Received msg on oxc" << endl;
    if (msg->isSelfMessage()) {
        // The msg is a oxc_ack_msg, then send to controller
        msg->setKind(SWITCH_ACK_MSG);
        send(msg, "controller_port$o");

    } else if (input_port == -1) {
        error("Input Gate error");

    } else if (msg->getKind() == ROUTE_ALLOC_MSG || ra_msg != nullptr) {
        RouteVecMsg *ra_msg = dynamic_cast<RouteVecMsg *> (msg);
        assert(ra_msg->getTargetId() == getId());
        for (auto routes : ra_msg->getNew_route_table()) {
            PortStatus tmp_port_status = {
                .dest_port = routes.outPort,
                .switch_finish_time = omnetpp::simTime() + switch_delay};
            route_table[routes.inPort] = tmp_port_status;
        }
        delete ra_msg;

    } else if (msg->getKind() == OXC_CONF_MSG || oxc_msg != nullptr) {
        EV << "Received OXC_CONF_MSG" << endl;
        OXCConfMsg *oxc_msg = dynamic_cast<OXCConfMsg *> (msg);
        for (auto connection : oxc_msg->getChange_set().config_to_remove) {
            PortStatus tmp_port_status = {
                .dest_port = -1,
                .switch_finish_time = omnetpp::simTime()  + switch_delay };
            route_table[connection.in_port_id] = tmp_port_status;
        }
        for (auto connection : oxc_msg->getChange_set().config_to_add) {
            PortStatus tmp_port_status = {
                .dest_port = connection.out_port_id,
                .switch_finish_time = omnetpp::simTime() + switch_delay };
            route_table[connection.in_port_id] = tmp_port_status;
        }
        SwitchAckMsg *oxc_ack_msg = new SwitchAckMsg;
        oxc_ack_msg->setKind(SWITCH_ACK_MSG);
        oxc_ack_msg->setThe_flow(oxc_msg->getThe_flow());
        if((oxc_msg->getChange_set().config_to_remove.size() != 0) 
            || (oxc_msg->getChange_set().config_to_add.size() != 0))
            scheduleAt(omnetpp::simTime() + switch_delay, oxc_ack_msg);
        else {
            scheduleAt(omnetpp::simTime(), oxc_ack_msg);
        }
        delete oxc_msg;
    } else if (msg->arrivedOn("optical_port$i")) {
        // Data msgs
        if (route_table.find(input_port) == route_table.end()) {
            error("No such route of input port %d", input_port);

        } else if (route_table[input_port].switch_finish_time
                <= omnetpp::simTime()) {
            EV << "Send a data msg from machine"<< std::endl;
            send(msg, route_table[input_port].dest_port);

        } else {
            delete msg;
            cout << "I am " << getFullPath() << endl;
            cout << "This msg is from port " << input_port << endl;
            cout << "Which is connected to " << gate(input_port)->getPathStartGate()->getFullPath() << endl;
            cout << "switch start time" << route_table[input_port].switch_finish_time - switch_delay << endl;
            std::cout << "Switch finish time " << route_table[input_port].switch_finish_time << endl;
            std::cout << "now time " << omnetpp::simTime() << endl;
            error("NOT FINISHED SWITCH YET");
        }
    } else {
        EV << "msg kind " << msg->getKind() << endl;
        std::cout << "Msg arrived on " << msg->getArrivalGate()->getFullPath() << endl;
        error("Unknown msg kind");
    }
}

}  // namespace ai_sim
